package org.misty.bloomfilter;

import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisNoScriptException;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class JedisPoolAdapter implements BloomFilter.IRedisClient<JedisPoolAdapter.Script> {
    private final static ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();

    private final JedisPool pool;

    public JedisPoolAdapter(JedisPool pool) {
        Objects.requireNonNull(pool);
        this.pool = pool;
    }

    @Override
    public Script scriptLoad(String script, Class<?> resultType) {
        Object sc = cache.get(script);
        if (!(sc instanceof Script)) {
            while (true) {
                Object old = cache.putIfAbsent(script, Boolean.TRUE);
                if (old == Boolean.TRUE) { // 没抢到锁，概率最高
                    Thread.yield();
                } else if (old instanceof Script) {
                    sc = old;
                    break;
                } else if (old == null) {
                    String sha;
                    try (Jedis client = pool.getResource()) {
                        sha = client.scriptLoad(script);
                    }
                    var ns = new Script(script, sha);
                    log.debug("load script: {}\n<<<===\n{}\n===>>>", ns.sha, script);
                    cache.put(script, sc = ns);
                    break;
                } else {
                    throw new RuntimeException("不兼容的数据: " + old);
                }
            }
        }
        return (Script) sc;
    }

    @Override
    public Object eval(String script, Class<?> resultType, List<String> keys, String... args) {
        Script sc;
        Object ret;
        while (true) {
            sc = scriptLoad(script, resultType);
            try (Jedis client = pool.getResource()) {
                ret = client.evalsha(sc.sha, keys, Arrays.asList(args));
                break;
            } catch (JedisNoScriptException e) {
                cache.remove(script, sc);
            }
        }
        return ret;
    }

    static class Script {
        String lua;
        String sha;

        Script(String lua, String sha) {
            this.lua = lua;
            this.sha = sha;
        }
    }
}
